package com.wangm.easyExcel.listener;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.property.ExcelReadHeadProperty;
import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.wangm.easyExcel.ExcelErrorMessage;
import com.wangm.easyExcel.annotation.ExcelCheck;
import com.wangm.easyExcel.entity.ValidateAnnotation;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 具有校验功能的导入listener
 *
 * @author wangmeng
 * @since 2024/5/25
 */
@Slf4j
public class CheckImportListener<T> implements ReadListener<T> {


    /**
     * excel数据
     */
    protected final List<T> list = new ArrayList<>();
    /**
     * 错误信息集合
     */
    @Getter
    private final List<ExcelErrorMessage> errorList = new ArrayList<>();
    /**
     * 字段元数据
     */
    private final Map<Field, ValidateAnnotation> fieldMap = new HashMap<>();


    private boolean existValidate = true;


    public CheckImportListener() {
    }


    /**
     * 初始化信息.
     *
     * @param headMap headMap
     * @param context context
     */
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        ExcelReadHeadProperty excelReadHeadProperty = context.currentReadHolder().excelReadHeadProperty();
        for (Head head : excelReadHeadProperty.getHeadMap().values()) {
            Field field = head.getField();
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
            ExcelCheck excelCheck = field.getAnnotation(ExcelCheck.class);
            if (excelProperty != null && excelCheck != null && anyCheck(excelCheck)) {
                String headName = String.join("-", head.getHeadNameList());
                field.setAccessible(true);
                fieldMap.put(field, new ValidateAnnotation(excelProperty, excelCheck, headName));
            }
        }
        if (fieldMap.isEmpty()) {
            existValidate = false;
        }
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("解析单元格失败,", exception);
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            log.error("第{}行，第{}列解析异常，数据为:{}", excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(), JSON.toJSONString(excelDataConvertException.getCellData()));
        }
    }

    @Override
    public void invoke(T data, AnalysisContext context) {
        log.debug("data:{}", JSON.toJSONString(data));
        list.add(data);
        if (existValidate) {
            Integer rowIndex = context.readRowHolder().getRowIndex();
            try {
                // 校验是否空值
                checkEmpty(data, rowIndex);
                // 校验长度
                checkLength(data, rowIndex);
            } catch (Exception e) {
                log.error("校验excel信息失败,", e);
            }
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        try {
            checkRepeat();
        } catch (Exception e) {
            log.error("校验重复信息失败,", e);
        }
        errorList.sort(Comparator.comparing(ExcelErrorMessage::getRowNum));
    }


    /**
     * 检验非空
     *
     * @param data     数据
     * @param rowIndex 行索引
     */
    public void checkEmpty(T data, Integer rowIndex) {
        fieldMap.forEach(((field, annotation) -> {
            ExcelCheck excelCheck = annotation.getExcelCheck();
            try {
                Object value = field.get(data);
                //校验非空
                if (!excelCheck.canEmpty()) {
                    if (value == null ||
                            (field.getType() == String.class && StringUtils.isEmpty((String) value))) {
                        addError(rowIndex + 1, annotation.getHeadName() + "字段不能为空!");
                    }
                }
            } catch (IllegalAccessException e) {
                log.error("校验excel信息失败,", e);
                throw new RuntimeException(e);
            }
        }));
    }


    /**
     * 校验长度
     *
     * @param data     数据
     * @param rowIndex 行索引
     */
    public void checkLength(T data, Integer rowIndex) {
        fieldMap.forEach(((field, annotation) -> {
            ExcelCheck excelCheck = annotation.getExcelCheck();
            try {
                if (excelCheck.length() > 0 && field.getType() == String.class) {
                    String value = (String) field.get(data);
                    if (value.length() > excelCheck.length()) {
                        addError(rowIndex+1, annotation.getHeadName() + "字段长度大于" + excelCheck.length() + "!");
                    }
                }
            } catch (IllegalAccessException e) {
                log.error("校验excel信息失败,", e);
                throw new RuntimeException(e);
            }
        }));
    }


    /**
     * 检验重复
     */
    public void checkRepeat() {
        Map<Field, ValidateAnnotation> repeatFieldMap = fieldMap.entrySet().stream().filter(entry -> !entry.getValue().getExcelCheck().canRepeat())
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        repeatFieldMap.forEach((field, annotation) -> {
            //使用iterate方式构建流以获取行号
            Stream.iterate(0, i -> i + 1).limit(list.size()).collect(Collectors.groupingBy(i -> {
                        try {
                            Object value = field.get(list.get(i));
                            return JSON.toJSONString(value);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    }, LinkedHashMap::new, Collectors.mapping(i -> i + 2, Collectors.toList())))
                    .forEach((value, indexList) -> {
                        if (indexList.size() > 1) {
                            for (int i = 0; i < indexList.size(); i++) {
                                if (i == 0) {
                                    continue;
                                }
                                addError(indexList.get(i), annotation.getHeadName() + "字段重复!");
                            }
                        }
                    });
        });
    }


    public void addError(Integer index, String errorMessage) {
        ExcelErrorMessage excelErrorMessage = new ExcelErrorMessage().setRowNum(index).setMessage(errorMessage);
        errorList.add(excelErrorMessage);
    }


    /**
     * 存在任何校验
     *
     * @param excelCheck excelCheck
     * @return 存在任何校验
     */
    private Boolean anyCheck(ExcelCheck excelCheck) {
        if (!excelCheck.canEmpty()) {
            return true;
        }
        if (excelCheck.length() != -1) {
            return true;
        }
        if (!excelCheck.canRepeat()) {
            return true;
        }
        return false;
    }
}
